package jmine.tec.script.persistent.mock;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.script.ScriptExecutionContext;
import jmine.tec.script.ScriptExecutor;
import jmine.tec.script.ScriptExecutorException;

/**
 * Executor de Mock
 * 
 * @author lundberg
 */
@Unmodifiable
public class MockExecutor implements ScriptExecutor {

    private static final int MEM_SIZE = 30000;

    /**
     * {@inheritDoc}
     */
    public <R> R execute(ScriptExecutionContext<R> context) throws ScriptExecutorException {
        InputStream input = (InputStream) context.getParameters().getParameterValues().get("input");
        OutputStream output = (OutputStream) context.getParameters().getParameterValues().get("output");
        int[] memory = new int[MEM_SIZE];
        Arrays.fill(memory, 0);
        int pointer = 0;
        int instruction = 0;
        char[] script = context.getScript().toCharArray();
        try {
            while (instruction >= 0 && instruction < script.length) {
                switch (script[instruction]) {
                case '>': // > increment the data pointer (to point to the next cell to the right).
                    pointer++;
                    break;
                case '<': // < decrement the data pointer (to point to the next cell to the left).
                    pointer--;
                    break;
                case '+': // + increment (increase by one) the byte at the data pointer.
                    memory[pointer]++;
                    break;
                case '-': // - decrement (decrease by one) the byte at the data pointer.
                    memory[pointer]--;
                    break;
                case '.': // . output the value of the byte at the data pointer.
                    output.write(memory[pointer]);
                    break;
                case ',': // , accept one byte of input, storing its value in the byte at the data pointer.
                    memory[pointer] = input.read();
                    break;
                case '[': // [ if the byte at the data pointer is zero, then instead of moving the instruction pointer forward to the next
                    // command, jump it forward to the command after the matching ] command*.
                    if (memory[pointer] == 0) {
                        int c = 1;
                        while (instruction < script.length && c != 0) {
                            switch (script[instruction + 1]) {
                            case '[':
                                c++;
                                break;
                            case ']':
                                c--;
                                break;
                            default:
                                // OK
                            }
                            instruction++;
                        }
                    }
                    break;
                case ']': // ] if the byte at the data pointer is nonzero, then instead of moving the instruction pointer forward to the
                    // next command, jump it back to the command after the matching [ command*.
                    if (memory[pointer] != 0) {
                        int c = 1;
                        while (instruction >= 0 && c != 0) {
                            switch (script[instruction - 1]) {
                            case ']':
                                c++;
                                break;
                            case '[':
                                c--;
                                break;
                            default:
                                // OK
                            }
                            instruction--;
                        }
                    }
                    break;
                default:
                    // OK
                }
                instruction++;
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
        return context.getResult();
    }

    /**
     * {@inheritDoc}
     */
    public <R> R getDependencies(ScriptExecutionContext<R> context) throws ScriptExecutorException {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    public <R> R getValidations(ScriptExecutionContext<R> context) throws ScriptExecutorException {
        throw new UnsupportedOperationException();
    }

}
